home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Gekikoh Dennoh Club 1
/
Gekikoh Dennoh Club Vol. 1 (Japan).7z
/
Gekikoh Dennoh Club Vol. 1 (Japan) (Track 1).bin
/
kowin
/
archive
/
sys
/
kowin14d.lzh
/
doc
/
programming
/
kowin_p7.doc
< prev
next >
Wrap
Text File
|
1995-09-15
|
8KB
|
227 lines
Ko-Window プログラミング、入門編 その5 「デバッグのために」
プログラミングとは切っても切れない間柄のデバッグ作業。それについてもここで
少し触れてみましょう。
●コンパイルエラーとバグ
コンパイル時に出てくるエラーはバグ以前の問題で、いわゆる文法エラーです。こ
れは慣れた人なら、メッセージが出た瞬間その原因がひらめき、ささっとソースを修
正して直してしまうことでしょう。エラーメッセージが流れるほど出てくることもあ
りますが、いちいち全部追い掛ける必要はありません。たいてい、最初の文法エラー
が原因でその後のコンパイルに影響を与えてるだけなので、最初の要因を直してから、
コンパイル、を繰り返せば途端にエラーが減っていくはずです。
ただソースリストが大きかったりすると、何度もコンパイルしたりエディタで編集
したりするのは時間もかかり、結構やっかいです。ところがこの作業は、工夫しだい
でかなり効率良くデバッグできるようになります。それが分割コンパイルです。
●分割コンパイル
分割して1つ1つのソースを小さくすることは、開発に対してさまざまな利点をもた
らします。
・エラー箇所が分かりやすくなる
分割して関連ある部分ごとまとソースにめてあるため、エラー箇所の絞り込
みが容易で修正しやすくなります。またグローバル参照の変数や関数を減ら
し、個々に閉じたプログラムを書くことができるため、シンボル衝突を防ぐ
と同時に保守性を高める効果もあります。
・コンパイル時間が短縮される
これは分割における最大のメリットの1つです。デバッグによる修正も書き換
えたソースのみ再コンパイルすれば済むので、コンパイル時間は大幅に減少
されます。1番時間のかかるコンパイル処理が、だいたい 1/分割数 で済むわ
けですから、どれだけ効果が大きいかわかるでしょう。
・消費メモリが少なくなる
一度にコンパイルする容量が小さくなるため、コンパイラの消費するメモリ
が小さくなります。だいたいフリーエリアが 1.5M あれば、make 上で余裕
でコンパイルできるはずです。メモリの少ないマシンではもちろん、メモリ
をかなり増設しているマシンでも、これで浮いたメモリをディスクキャッシュ
や RAMDISK に回せば、ますます速いコンパイルができるようになるでしょう。
このように、徹底した分割コンパイルは、開発効率を最大限あげることにつながり
ます。
もちろん make は必須のコマンドです。これ無くして分割コンパイルはできません。
gnumake もしくは XC v2 付属の make が使えます。(gnumake を使う場合は消費メ
モリが増えるので、メモリのフリーエリアに注意して下さい)
経験上、X68000 では1つのソースは大体 2~3Kbyte、どんなに大きくなっても
10Kbyte を越えてはいけません。私が今まで書いてきた厖大な量の Ko-Window アプ
リケーションが、極めて短期間の間に組まれていることを見れば、分割開発にどれだ
け意義があるのかわかるのではないでしょうか。もちろん当初使っていたマシンは
10MHz 無改造の X68000 です。初期の厖大なプログラムは全部マシンで組みました。
●やっかいなバグとは
本当にやっかいなバグというのは、コンパイルが問題なく通った後にはじめて現れ
ます。いきなりバスエラーで止まるもの、起動したものの思い通りに動かないもの、
そして1番難解なものは、ちゃんと動くようにみえるのに何度もやってるとたまに失
敗する、というものです。
デバッグに必要な情報は2つ、
バグのある場所を特定する
バグの原因を究明する
デバッグの難しさは、この2つがどれだけ絞り込めるかに大きく依存しています。
デバッグにどこから手をつけていいかわからないという場合は、まずこのバグのあ
る場所の場所を限定することから手をつけるのが得策です。
●イベントスタック表示を見る
Ko-Window でシステムエラーが発生した場合、エラーウィンドウにその時のイベン
トスタックのダンプ表示を行わせることができます。これを見ておくと、だいたいど
のイベント発生時に起こったものか、というのを判断することができます。
これは wsrv.x の起動時にオプションで指定します。詳しくは基本セット付属の
wsrv.doc を参照して下さい。
wsrv.x -d1 エラー時にイベントスタックの表示を行う
wsrv.x -d3 デバッグモードで起動する
wsrv.x -d7 トレースモードで起動する
●プログラムが思いどおりに動かない場合、どうやってデバッグをしますか?
私の場合はまず、怪しいと思われるコード部分とじーっと睨めっこします。もちろん
頭の中でプログラムのトレースをしているわけです。
バグの発生しやすい場所は、アドレス(ポインタ)、スタック、ファイル、です。も
しバスエラーやアドレスエラーが発生する場合であれば、原因はこの中に収まる可能
性が極めて大です。
それで原因がわからなかった時は、エラー箇所の絞り込み、を行います。少なくと
もどこまではプログラムがちゃんと動いたか、またはその時の変数の値でもわかれば、
要因を探る材料にもなります。
コードの節目に printf() 等を徹底して埋め込み、場合によっては変数値を表示さ
せながら動かし、場所の特定を行います。ただし、この printf() というのはウィン
ドウプログラムでは使えません(標準入出力がないためです)。そこで代わりに Console
というのを用います。
● Console への出力
ウィンドウ画面では、同時に複数のプログラムが動いているためどこに文字列を表
示する、という特定ができません。そこでどれか1つのウィンドウを代表者と決め、
ちょっとしたエラーメッセージ等の文字列表示をすべてそこに行わせることにします。
その代表者を Console といいます。
現在 Console として使えるプログラムは、Command.win と KoConsole.win の2つで
す。Command.win は、-tConsole というオプションをつけたウィンドウだけが Console
になります。KoConsole.win は Console の出力表示専用です。もちろん、Console は
ウィンドウ上に 1つしか存在できません。
Console へ文字列を表示させるのは簡単です。ウィンドウプログラムの好きな場所
に、次のような行を加えてみて下さい。
ConsoleOpen();
ConsolePrint( "Consoleへの文字列表示\r\n" );
Close は必要ありません。また ConsoleOpen() は何度実行しても大丈夫です。とに
かく ConsolePrint() の前に1回以上 ConsoleOpen() を書いておけば正しく Console
に出力が行われるはずです。
printf() のようなフォーマット付き出力を行う場合は、ConsolePrintf() を使いま
す。例えば
ConsoleOpen();
ConsolePrintf( "i=%d\r\n", i );
となります。
Console について詳しくは、別ドキュメントで触れたいと思います。ここではデバッ
グ時に活用できる、ということで覚えておいて下さい。
● Ko-Window でひっかかりやすいポイント
ありがち用例集
・fopen() が失敗する
fopen() が原因不明で失敗する、などという場合は HEAP エリアの確保をし
ていない可能性があります。グローバル変数 WindowHeapSize の設定を行っ
ているかどうか再確認してみて下さい。 -> file.doc 参照
・表示文字列が崩れる
DrawSet~() の関数は、実際の表示は行わずにワークエリアに情報を格納す
るだけに過ぎません。表示そのものは WindowDraw() によってはじめて行わ
れるため、それを忘れているとうっかり次のようなプログラムを書いてしま
うことがあります。
drawset( dbuf, num1, num2 )
DrawBuf *dbuf;
{
char buf[100];
sprintf( buf, "%d", num1 );
DrawSetSymbol( dbuf, X1, Y1, buf, ATTR, FONT );
sprintf( buf, "%d", num2 );
DrawSetSymbol( dbuf+1, X2, Y2, buf, ATTR, FONT );
}
これはどこが悪いかわかりますでしょうか。まずい点は2つあります。
1) 文字列領域 buf が重なっている
これを実際に動かすと、num2 の値が2つ表示されてしまうことでしょ
う。つまり DrawSetSymbol() は dbuf という変数に座標と一緒に
buf のアドレスを書き込んでいるに過ぎません。buf の中身を参照
するのはずっとあとになってからです。だから buf の同一アドレス
が2度書き込まれ、2度目の sprintf() で書き換えた内容のみあと
から参照されてしまうわけです。
2) auto 変数領域のアドレスを呼び出し元へ返している
スタック上に確保された文字列バッファ buf のアドレスを、呼び出
し元に返しています。リターンとともにスタックフレームは消滅し
ますので、これではそのスタックに別の値が書き込まれてもおかし
くはありません。
というわけで、正しくは(もしこのまま修正するなら)次のようになります。
drawset( dbuf, num1, num2 )
DrawBuf *dbuf;
{
static char buf1[100],
buf2[100];
sprintf( buf, "%d", num1 );
DrawSetSymbol( dbuf, X1, Y1, buf1, ATTR, FONT );
sprintf( buf, "%d", num2 );
DrawSetSymbol( dbuf+1, X2, Y2, buf2, ATTR, FONT );
}
--
1994 9/01 作成
1995 9/15 加筆修正
小笠原博之
oga@dgw.yz.yamagata-u.ac.jp
DenDenNET: DEN0006 COR.